package edu.hzau.xiahui.api;


import com.google.common.collect.Lists;
import com.netease.lowcode.core.annotation.NaslLogic;
import edu.hzau.xiahui.vo.CalculateParam;
import edu.hzau.xiahui.vo.CalculateResult;
import edu.hzau.xiahui.vo.InsuranceThreshold;
import edu.hzau.xiahui.vo.TaxStep;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Objects;
import java.util.Optional;

import static edu.hzau.xiahui.constants.Constants.*;

@Component
public class CalculateApi {
    @NaslLogic
    public CalculateResult calculate(CalculateParam param) {
        CalculateResult res = new CalculateResult();

        res.salary = param.salary;
        res.bonus = param.bonus;
        res.yearSalary = param.salary.multiply(new BigDecimal(YEAR));
        res.incomeBeforeTax = res.yearSalary.add(res.bonus);

        deduct(param, res);
        insurance(param, res);
        tax(param, res);

        if (param.bonusAlone) {
            res.incomeAfterTax = res.incomeBeforeTax.subtract(res.salaryTax.add(res.bonusTax)).subtract(res.insurance);
        } else {
            res.incomeAfterTax = res.incomeBeforeTax.subtract(res.incomeTax).subtract(res.insurance);
        }

        return res;
    }

    private void deduct(CalculateParam param, CalculateResult res) {
        Optional.ofNullable(param.childEdu).ifPresent(x -> res.childEdu = x.multiply(new BigDecimal(YEAR)));
        Optional.ofNullable(param.continueEdu).ifPresent(x -> res.continueEdu = x.equals(DEGREE_EDU) ? x.multiply(new BigDecimal(YEAR)) : x);
        Optional.ofNullable(param.criticalIllness).ifPresent(x -> res.criticalIllness = x);
        Optional.ofNullable(param.houseLoan).ifPresent(x -> res.houseLoan = x.multiply(new BigDecimal(YEAR)));
        Optional.ofNullable(param.houseRent).ifPresent(x -> res.houseRent = x.multiply(new BigDecimal(YEAR)));
        Optional.ofNullable(param.parentSupport).ifPresent(x -> res.parentSupport = x.multiply(new BigDecimal(YEAR)));
        Optional.ofNullable(param.childSupport).ifPresent(x -> res.childSupport = x.multiply(new BigDecimal(YEAR)));
        res.deduct = Lists.newArrayList(res.childEdu, res.continueEdu, res.criticalIllness, res.houseLoan, res.houseRent, res.parentSupport, res.childSupport).stream()
                .filter(Objects::nonNull).reduce(BigDecimal::add).orElse(null);
    }

    private void insurance(CalculateParam param, CalculateResult res) {
        InsuranceThreshold insuranceThreshold = INSURANCE_THRESHOLDS.get("wuhan");
        BigDecimal insuranceBase = res.incomeBeforeTax.compareTo(insuranceThreshold.insurance[0].multiply(new BigDecimal(YEAR))) < 0
                ? insuranceThreshold.insurance[0].multiply(new BigDecimal(YEAR))
                : res.incomeBeforeTax.compareTo(insuranceThreshold.insurance[1].multiply(new BigDecimal(YEAR))) > 0
                ? insuranceThreshold.insurance[1].multiply(new BigDecimal(YEAR))
                : res.incomeBeforeTax;
        res.pensionInsurance = insuranceBase.multiply(param.pensionInsurance);
        res.healthInsurance = insuranceBase.multiply(param.healthInsurance);
        res.unemploymentInsurance = insuranceBase.multiply(param.unemploymentInsurance);

        BigDecimal houseFundBase = res.incomeBeforeTax.compareTo(insuranceThreshold.houseFund[0].multiply(new BigDecimal(YEAR))) < 0
                ? insuranceThreshold.houseFund[0].multiply(new BigDecimal(YEAR))
                : res.incomeBeforeTax.compareTo(insuranceThreshold.houseFund[1].multiply(new BigDecimal(YEAR))) > 0
                ? insuranceThreshold.houseFund[1].multiply(new BigDecimal(YEAR))
                : res.incomeBeforeTax;
        res.houseFund = houseFundBase.multiply(param.houseFund);

        Optional.ofNullable(param.enterpriseAnnuity).ifPresent(x -> res.enterpriseAnnuity = x.multiply(new BigDecimal(YEAR)));

        res.insurance = Lists.newArrayList(res.pensionInsurance, res.healthInsurance, res.unemploymentInsurance, res.houseFund, res.enterpriseAnnuity).stream()
                .filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    private void tax(CalculateParam param, CalculateResult res) {
        res.salaryTaxBase = res.yearSalary.subtract(YEAR_TAX_FREE).subtract(res.insurance).subtract(Optional.ofNullable(res.deduct).orElse(BigDecimal.ZERO));
        TaxStep salaryTaxStep = YEAR_TAX_STEPS.stream().filter(x -> res.salaryTaxBase.compareTo(x.threshold) <= 0).findFirst().orElseThrow(IllegalArgumentException::new);
        res.salaryTaxRate = salaryTaxStep.taxRate;
        res.salaryTaxDeduct = salaryTaxStep.deduct;
        res.salaryTax = res.salaryTaxBase.multiply(res.salaryTaxRate).subtract(res.salaryTaxDeduct);

        TaxStep bonusTaxStep = BONUS_TAX_STEPS.stream().filter(x -> param.bonus.divide(new BigDecimal(YEAR), RoundingMode.UP).compareTo(x.threshold) <= 0).findFirst().orElseThrow(IllegalArgumentException::new);
        res.bonusTaxRate = bonusTaxStep.taxRate;
        res.bonusTaxDeduct = bonusTaxStep.deduct;
        res.bonusTax = param.bonus.multiply(res.bonusTaxRate).subtract(res.bonusTaxDeduct);

        res.incomeTaxBase = res.incomeBeforeTax.subtract(YEAR_TAX_FREE).subtract(res.insurance).subtract(Optional.ofNullable(res.deduct).orElse(BigDecimal.ZERO));
        TaxStep incomeTaxStep = YEAR_TAX_STEPS.stream().filter(x -> res.incomeTaxBase.compareTo(x.threshold) <= 0).findFirst().orElseThrow(IllegalArgumentException::new);
        res.incomeTaxRate = incomeTaxStep.taxRate;
        res.incomeTaxDeduct = incomeTaxStep.deduct;
        res.incomeTax = res.incomeTaxBase.multiply(res.incomeTaxRate).subtract(res.incomeTaxDeduct);
    }
}
